#include <thread>
#include <vector>
#include <iostream>
#include <string>
#include <fstream>
#include <cassert>

#include <Windows.h>
#include <Wingdi.h>

#include <GL/glew.h>
#include <GL/wglew.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include "Renderer.hpp"
#include "Window.hpp"
#include "Scene.hpp"
#include "Event.hpp"

#include "Debug.hpp"

#if 0
GLuint vao;
::glGenVertexArrays(1, &vao);
::glBindVertexArray(vao);
::glBindVertexArray(vertexArray->arrayId[0]);

GLuint vbo[2];
::glGenBuffers(2, vbo);

::glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
::glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
::glEnableVertexAttribArray(0);
::glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, nullptr);


::glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
::glBufferData(GL_ARRAY_BUFFER, sizeof(colours), colours, GL_STATIC_DRAW);
::glEnableVertexAttribArray(1);
::glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, nullptr);

GLuint uniforms[3];
glGetUniformLocation(pipeline
glProgramUniformMatrix4fv(shader->shaderId, shader->uniforms[0], 1, GL_FALSE, &model[0][0]);
glProgramUniformMatrix4fv(shader->shaderId, shader->uniforms[1], 1, GL_FALSE, &view[0][0]); // Send our view matrix to the shader
glProgramUniformMatrix4fv(shader->shaderId, shader->uniforms[2], 1, GL_FALSE, &projection[0][0]); // Send our projection matrix to the shader
#endif

bool CheckForErrors()
{
	GLenum glError = ::glGetError();

	if(glError != GL_NO_ERROR)
	{
		std::cout << "GL Error: " << ::gluErrorString(glError) << std::endl;
#if defined _DEBUG || defined CRASH_ON_GL_ERROR
		std::terminate();
#endif
		return false;
	}

	return true;
}

RenderData::RenderData()
{
}

RenderData::~RenderData()
{
	delete scene;

	::wglMakeCurrent(nullptr, nullptr);
	::wglDeleteContext(hGLRC);

	delete window;
}

CRenderer::CRenderer(int glVerMajor, int glVerMinor, EventHandler& eventHandler) :
	state(RenderState::Initializing), eventHandler(eventHandler)
{
	thr = std::thread(&CRenderer::RenderThread, this);
}

CRenderer::~CRenderer()
{
	thr.join();

	std::for_each(renderTargets.begin(), renderTargets.end(), [&](RenderData* data)
	{
		delete data;
	});
}

bool CRenderer::InitializePixelFormat(CWindow* window)
{
	PIXELFORMATDESCRIPTOR pfd;
	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DOUBLEBUFFER|PFD_SUPPORT_OPENGL|PFD_DRAW_TO_WINDOW|PFD_GENERIC_ACCELERATED;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 24;
	pfd.cDepthBits = 24;
	pfd.cAlphaBits = 8;
	pfd.cStencilBits = 8;
	pfd.iLayerType = PFD_MAIN_PLANE;

	int pixelFormat = ::ChoosePixelFormat(window->hDC, &pfd);
	if(pixelFormat == 0)
		return false;

	if(::SetPixelFormat(window->hDC, pixelFormat, &pfd) != TRUE)
		return false;

	return true;
}

RenderData* CRenderer::CreateRenderContext(int glVerMajor, int glVerMinor, CWindow* window)
{
	auto hDC = window->hDC;

	if(!InitializePixelFormat(window))
		return nullptr;

	HGLRC hGLRC = ::wglCreateContext(hDC);
	if(!hGLRC || ::wglMakeCurrent(hDC, hGLRC) == FALSE)
		return nullptr;

	if(glewInit() != GLEW_OK)
		return nullptr;

	if(!WGLEW_ARB_create_context)
	{
		::wglMakeCurrent(NULL, NULL);
		::wglDeleteContext(hGLRC);
		throw("WGL_ARB_create_context is not supported.");
	}

	bool debug = false;

	int attributes[] =
	{
		WGL_CONTEXT_MAJOR_VERSION_ARB, glVerMajor,
		WGL_CONTEXT_MINOR_VERSION_ARB, glVerMinor,
		WGL_CONTEXT_FLAGS_ARB, !debug ? WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB : 
		WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB,
		WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
		0
	};

	if(auto hNewGLRC = ::wglCreateContextAttribsARB(hDC, NULL, attributes))
	{
		::wglMakeCurrent(NULL, NULL);
		::wglDeleteContext(hGLRC);
		if(::wglMakeCurrent(hDC, hNewGLRC) == FALSE)
			return nullptr;

		hGLRC = hNewGLRC;
	}
	else
	{
		::wglMakeCurrent(NULL, NULL);
		::wglDeleteContext(hGLRC);
		return nullptr;
	}

	::glGetIntegerv(GL_MAJOR_VERSION, &glVersion[0]);
	::glGetIntegerv(GL_MINOR_VERSION, &glVersion[1]);

	if(!WGLEW_EXT_swap_control)
	{
		::wglMakeCurrent(NULL, NULL);
		::wglDeleteContext(hGLRC);
		throw("WGL_EXT_swap_control is not supported.");
	}

	RenderData *renderData = new RenderData;
	renderData->window = window;
	renderData->hGLRC = hGLRC;

	std::cout << "Using OpenGL: " << glVersion[0] << "." << glVersion[1] << std::endl;

	return renderData;
}

bool CRenderer::Render(CScene* scene)
{
	using namespace std::chrono;

	static int frames = 0;
	static auto tick = high_resolution_clock::now();

	scene->Draw();

	auto now = high_resolution_clock::now();
	auto diff = duration_cast<milliseconds>(now - tick);
	++frames;

	if(diff.count() >= 1000)
	{
		double secs = (double)diff.count() * 0.001;

		std::cout << "Frames " << frames << "/" << secs << "secs = " << (double)frames/secs << "FPS" << std::endl;
		tick = now;
		frames = 0;
	}

	return true;
}

bool CRenderer::Run()
{
	bool shutdown = false;
	bool working = false;

	std::unique_lock<std::mutex> lk(GetLock());

	while(!shutdown)
	{
		if(!lk.owns_lock())
			lk.lock();

		switch(state)
		{
		case CRenderer::RenderState::Initializing:
			break;

		case CRenderer::RenderState::AddRenderTargets:
			{
				RenderData* renderData;
				for(auto it(newTargetWindows.begin()), end(newTargetWindows.end()); it != end; ++it)
				{
					renderData = CreateRenderContext(4, 1, *it);
					if(renderData == nullptr)
					{
						state = CRenderer::RenderState::Shutdown;
						continue;
					}

					if(!WGLEW_EXT_swap_control)
					{
						state = CRenderer::RenderState::Shutdown;
						continue;
					}
					::wglSwapIntervalEXT(0);

					SceneData* sceneData = new SceneData(eventHandler, *this, renderData);
					MyScene* scene = new MyScene(sceneData);
					scene->Initialize();
					renderData->scene = scene;
					renderTargets.push_back(renderData);
				}
				newTargetWindows.clear();

				state = CRenderer::RenderState::Render;
				continue;
			}

		case CRenderer::RenderState::RemoveRenderTargets:
			{
				RenderData* renderData = nullptr;
				CWindow* window = nullptr;

				bool removed = false;

				for(auto it(oldTargetWindows.begin()), end(oldTargetWindows.end()); it != end; ++it)
				{
					for(auto it2(renderTargets.begin()), end2(renderTargets.end()); it2 != end2; ++it2)
					{
						if(*it == (*it2)->window)
						{
							window = *it;
							renderData = *it2;
							renderTargets.erase(it2);
							delete renderData;

							removed = true;
							break;
						}
					}
					if(removed)
						break;
				}
				oldTargetWindows.clear();

				if(renderTargets.size() == 0)
					state = CRenderer::RenderState::Shutdown;
				else
					state = CRenderer::RenderState::Render;

				continue;
			}

		case CRenderer::RenderState::Render:
			{
				while(state == CRenderer::RenderState::Render)
				{
					if(!lk.owns_lock())
						lk.lock();

					if(renderTargets.size() == 0)
					{
						state = CRenderer::RenderState::Shutdown;
						continue;
					}

					for(auto it(renderTargets.begin()), end(renderTargets.end()); it != end; ++it)
					{
						//if(lk.owns_lock())
						//	lk.unlock();
						if(!Render((*it)->scene))
						{
							state = CRenderer::RenderState::Shutdown;
							continue;
						}
						//lk.lock();
					}
					if(lk.owns_lock())
						lk.unlock();
				}
			}
			break;

		case CRenderer::RenderState::Shutdown:
			shutdown = true;
			break;
		}

		if(lk.owns_lock())
			lk.unlock();
	}

	return true;
}

int CRenderer::RenderThread()
{
	eventHandler.AddListener(EventType::WindowCreated, std::bind(&CRenderer::OnWindowCreated, this, std::placeholders::_1));
	eventHandler.AddListener(EventType::WindowDestroyed, std::bind(&CRenderer::OnWindowDestroyed, this, std::placeholders::_1));
	
	WindowOptions windowOptions = { 0, 0, 1024, 768, 8, 8, 8, 8, 8, 8, 0 };
	CWindow* window = new CWindow(&windowOptions, eventHandler);

	Run();

	eventHandler.Produce(EventType::Shutdown, (Shutdown*)nullptr);

	return 0;
}

bool CRenderer::OnWindowCreated(Event* evt)
{
	auto data = static_cast<WindowCreated*>(evt->data);

	std::unique_lock<std::mutex> lk(GetLock());

	newTargetWindows.push_back(data->window);

	state = CRenderer::RenderState::AddRenderTargets;

	return true;
}

bool CRenderer::OnWindowDestroyed(Event* evt)
{
	auto data = static_cast<WindowDestroyed*>(evt->data);

	std::unique_lock<std::mutex> lk(GetLock());

	oldTargetWindows.push_back(data->window);

	state = CRenderer::RenderState::RemoveRenderTargets;

	return true;
}
